<script>
  import $ from '../dfish.js'
  import {
    h,
    mergeProps,
    toRaw,
  } from 'vue/dist/vue.esm-bundler.js'

  const bindCache = {}

  export default {
    props: ['id', 'data', 'template', 'preload', 'width', 'height', 'colspan', 'rowspan', 'on', 'tip', 'ownproperty', 'orientUid', 'templateWrapper', 'preloadWrapper',
    ],
    beforeCreate() {
      this.isWidget = true
      if (this.templateWrapper || this.preloadWrapper) {
        this.uid = (this.templateWrapper || this.preloadWrapper).uid
      } else {
        this.uid = this.orientUid || $.uid()
      }
      $.vueCache[this.uid] = this
    },
    created() {
      let dftX = this.getDefaultProp()
      let watchs = {}
      this.x = new Proxy(this.runtimeX, {
        get: (obj, prop) => {
          if (prop in obj) return obj[prop]
          if (this.$props[prop] !== undefined) return this.$props[prop]
          if (dftX.__config_uid != $.__config_uid) {
            dftX = this.getDefaultProp()
          }
          return dftX[prop]
        },
        set: (obj, prop, newval) => {
          if (!watchs[prop]) {
            watchs[prop] = this.$watch(prop, (v) => obj[prop] = v)
          }
          obj[prop] = newval
          return true
        }
      })
    },
    data() {
      return {
        runtimeX: {},
        events: null,
      }
    },
    methods: {
      x_default() {
        //implement
      },
      x_watch(props) {
        props.forEach(k => this.$watch(k, v => this.x[k] = v))
      },
      cmd(o, ...args) {
        return this.exec(o, args)
      },
      exec(cmd, args, feat) {
        if (typeof cmd === 'string') {
          cmd = $.vm(this).commands[cmd]
        }
        if (cmd) {
          if (typeof cmd === 'function') {
            return cmd(args, this)
          } else if (typeof cmd === 'object') {
            let ncm = $.extendDeep({}, cmd)
            feat && $.merge(ncm, feat)
            if ($.cmdHooks[ncm.type]) {
              return $.cmdHooks[ncm.type].call(this, ncm, args)
            }
          }
        }
      },
      trigger(ev, ...args) {
        let type = ev.runType || ev.type || ev
        this.x.on && this.x.on[type] && this.x.on[type](ev, ...args)
        let wr = this.closetWrapper(wg => {
          wg.events && wg.events.fireEvent(type)
          return wg.getOn(type)
        })
        if (wr.$.emitsOptions && (type in wr.$.emitsOptions))
          wr.$emit(...(typeof ev === 'string' ? [type] : [type, ev]), ...args)
        this.events && this.events.fireEvent(type)
      },
      wrapper() {
        return this.templateWrapper || this.preloadWrapper || this
      },
      closetWrapper(fn) {
        if (this.x.template == null && this.x.preload == null) return this
        let u = this.uid
        let p = this
        let r
        while (p = p.$parent) {
          if (u === (p.$attrs && p.$attrs.uid) || u === (p.uid)) {
            if (p.isWidget && (!fn || fn(p))) {
              r = p
            }
          }
        }
        return r || this
      },
      addEvent(type, fn, once) {
        if (!this.events) this.events = new $.EventListener(this)
        this.events.addEvent(type, fn, once)
      },
      fireEvent(type, args) {
        return this.events && this.events.fireEvent(type, args)
      },
      removeEvent(type, fn) {
        this.events && this.events.removeEvent(type, fn)
      },
      getDefaultProp() {
        let propsEntity = !(this.template || this.preload)
        let dftopt = !this.ownproperty && propsEntity && this.getDefaultOption(this.$attrs.class)
        let dftoptEntity = !(dftopt.template || dftopt.preload)
        return {
          __config_uid: $.__config_uid,
          ...(propsEntity && dftoptEntity && this.x_default()),
          ...dftopt,
          ...(this.$options.pubParentName && propsEntity && dftoptEntity &&
            $.lodash.omit(this.providePubParent && $.idsAny(this.$options.pubParentName, this.providePubParent.$options
              .name) && this.providePubParent.pub, 'template', 'preload'))
        }
      },
      // 获取widget的默认配置项 /@a -> cls, t -> type
      getDefaultOption(a, b) {
        b = b || this.$options.name
        let y = $.x.defaultOptions[b]
        if ($.dfclsCache[b]) {
          let c = this.$options.name + (a ? ' ' + a : '')
          let d = $.dfclsCache[b]
          let z
          for (let k in d) {
            $.idsAll(c, k, ' ') && $.mergeDeep(z || (z = {}), d[k])
          }
          z && (y = $.extendDeep(z, y))
        }
        return y || false
      },
      closest(a) {
        let p = this
        let b = typeof a
        do {
          if (b === 'string') {
            if (p.$options.name === a)
              return p
          } else if (b === 'function') {
            if (a(p))
              return p
          }
        } while (p = p.$parent)
      },
      contains(a) {
        return a && this.el().contains(a.isWidget ? a.el() : a)
      },
      el(a) {
        return a && /\W/.test(a) ? $.q(a, this.el())[0] : document.getElementById(this.uid + (a || ''))
      },
      next() {
        const e = this.el().nextElementSibling
        return e && $(e)
      },
      prev() {
        const e = this.el().previousElementSibling
        return e && $(e)
      },
      snapElem() {
        return this.el()
      },
      cmdTip(o) {
        let t = this.x.title
        this.exec($.extendDeep({}, o, typeof t === 'object' ? t : null, {
          type: 'Tip',
          hoverDrop: true
        }));
      },
      hasBubble(a) {
        if (!a) return false
        if (this.contains(a)) return true
        let d = $.dialog(a)
        if (d && d.commander) return this.hasBubble(d.commander)
      },
      bind(name, fn) {
        let c = bindCache[this.uid] || (bindCache[this.uid] = {})
        if (!c[name]) c[name] = fn || this[name]
        return c[name]
      },
      getOn(type) {
        return (this.$.vnode.props && this.$.vnode.props['on' + type.charAt(0).toUpperCase() + type.slice(1)]) || (this
          .x.on && this.x.on[type])
      },
      getKey() {
        return (this.templateWrapper || this).$.vnode.key
      },
      slots_children(name) {
        return $.slotsChildren(this.$slots, name)
      },
      hasChild() {
        return $.hasChild(this.$slots)
      },
      html_icon(src, cls, onclick) {
        let font = src.startsWith('.') && !src.includes('/')
        return font ? h('i', {
          class: [cls, src.replace(/\./g, '')],
          onclick,
        }) : h('img', {
          src,
          onclick,
          class: cls
        })
      },
      html_template() {
        let f = this.x.template
        if (typeof f === 'object') {
          return h(toRaw(f), $.extend({
            template: false,
            templateWrapper: this,
          }, this.$props))
        }
        let path = f
        const except = (ex) => {
          console.error(ex)
          this.x.template = {
            template: '<div><span style="color:red">' + path + '</span> &nbsp; 模板不存在</div>'
          }
        }
        if (f.startsWith('url(')) {
          path = $.strTo($.strFrom(f, '('), ')', true)
          $.ajax.get(path).then(res => {
            this.x.template = res
          }).catch(except)
        } else {
          const sfx = '.vue'
          path = f.startsWith('@/') ? f.slice(2) : $.x.templateDir + f
          if (!path.endsWith(sfx)) path += sfx
          import(`@/${path}`)
            .then(mod => {
              this.x.template = mod.default
            })
            .catch(except)
        }
      },
      html_preload() {
        let f = this.x.preload
        if (typeof f === 'object') return h(toRaw(f), $.extend({
          preload: false,
          preloadWrapper: this,
        }, this.$props), this.$slots)
        const sfx = '.vue'
        let path = f.startsWith('@/') ? f.slice(2) : $.x.preloadDir + f
        if (!path.endsWith(sfx)) path += sfx
        import(`@/${path}`)
          .then(mod => {
            this.x.preload = mod.default
          })
          .catch(ex => {
            console.error(ex)
            this.x.preload = {
              template: '<div><span style="color:red">' + path + '</span> &nbsp; 模板不存在</div>'
            }
          })
      },
      slot(name = 'default') {
        let wr = this.templateWrapper || this.preloadWrapper || this
        return wr.$slots[name] ? wr.$slots[name]() : []
      },
      html_slots() {
        return this.$slots
      },
      html_self(a) {
        return h(this.tagName || 'div', a ? mergeProps(this.html_attrs, this.html_props, this.$attrs) : mergeProps(this
          .html_attrs, this.html_props), this.html_slots())
      },
      html() {
        return this.html_self()
      }
    },
    computed: {
      dfish() {
        return $
      },
      isEntity() {
        return !this.template && !this.preload
      },
      ownerView() {
        let p = this.provideView
        while (p && (!p.isEntity || !p.isView)) p = p.provideView
        return p
      },
      pubParent() {
        return this.providePubParent && $.idsAny(this.$options.pubParentName, this.providePubParent.$options.name) &&
          this.providePubParent
      },
      parentNode() {
        return $(this.el().parentElement)
      },
      tagName() {
        return 'div'
      },
      html_attrs() {
        return {
          id: this.uid,
          class: ['w-w', this.$options.name, this.html_class, this.x.class],
          style: [{
            width: this.x.width && $.unitSize(this.x.width),
            height: this.x.height && $.unitSize(this.x.height),
            '--w-grid-colspan': !this.x.colspan || this.x.colspan == -1 ? null : `span ${this.x.colspan}`,
            '--w-grid-rowspan': !this.x.rowspan || this.x.rowspan == -1 ? null : `span ${this.x.rowspan}`,
          }, this.html_style, this.x.style],
          ...this.x.on && $.lodash.mapKeys($.lodash.omitBy(this.x.on, (v, k) => k.includes(':')), (v, k) => 'on' + k),
          'w-key': this.getKey(),
          'w-width': this.x.width === '*' ? '*' : null,
          'w-height': this.x.height === '*' ? '*' : null,
          title: this.x.tip,
        }
      },
      html_style() {
        // implements
      },
      html_class() {
        // implements
      },
      html_props() {
        // implements
      },
      html_binds() {
        return mergeProps(this.html_attrs, this.html_props, this.$attrs)
      }
    },
    render() {
      return this.x.template ? this.html_template() :
        this.x.preload ? this.html_preload() : this.html()
    }
  }
</script>
